Skip to content

feat: add Telnyx SMS adapter (#7811)#108

Open
deepshekhardas wants to merge 1 commit into
utopia-php:mainfrom
deepshekhardas:feat/7811-telnyx-adapter
Open

feat: add Telnyx SMS adapter (#7811)#108
deepshekhardas wants to merge 1 commit into
utopia-php:mainfrom
deepshekhardas:feat/7811-telnyx-adapter

Conversation

@deepshekhardas

@deepshekhardas deepshekhardas commented Mar 16, 2026

Copy link
Copy Markdown

Updates the Telnyx SMS adapter.

Changes

  • Refactored \Telnyx.php\ SMS adapter
  • Updated corresponding tests

Testing

  • Tests updated to match refactored adapter

Closes #7811

@coderabbitai

coderabbitai Bot commented Mar 16, 2026

Copy link
Copy Markdown

Walkthrough

The Telnyx SMS adapter's constructor parameter $from was changed from nullable to required non-nullable. The process() method was refactored to send individual API requests for each recipient instead of a single batch request. Request construction now uses string interpolation for headers and includes per-recipient values for from, to, and text fields. Delivery reporting and error handling were updated to track metrics per recipient. The corresponding test file was updated to enable the Telnyx sender, construct actual SMS objects using environment variables, and execute assertions instead of placeholder code.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat: add Telnyx SMS adapter' clearly and accurately summarizes the main change: adding a new SMS adapter for the Telnyx service provider.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The pull request description accurately describes the changeset: refactored Telnyx SMS adapter and updated corresponding tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/Messaging/Adapter/SMS/TelnyxTest.php`:
- Around line 16-22: The test fails because Telnyx is instantiated with too few
arguments and doesn't guard missing env vars; update the test in TelnyxTest to
first read and validate getenv('TELNYX_API_KEY'), getenv('TELNYX_TO') and
getenv('TELNYX_FROM') and call markTestSkipped (or return) if any are empty,
then construct the Telnyx sender passing the required from argument (e.g.,
include the from value when calling new Telnyx(...)) and create the SMS using
the validated env values before calling send.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea64e157-ee53-4fa5-baaf-f297eaae6463

📥 Commits

Reviewing files that changed from the base of the PR and between fcb4c3c and e416217.

📒 Files selected for processing (2)
  • src/Utopia/Messaging/Adapter/SMS/Telnyx.php
  • tests/Messaging/Adapter/SMS/TelnyxTest.php

Comment on lines +16 to +22
$sender = new Telnyx(\getenv('TELNYX_API_KEY'));

// $message = new SMS(
// to: ['+18034041123'],
// content: 'Test Content',
// from: '+15005550006'
// );
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
from: \getenv('TELNYX_FROM')
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Pass the required Telnyx from argument and guard missing env vars.

This test currently instantiates Telnyx with too few arguments, which will fail before the send call runs. It also should skip cleanly when Telnyx env vars are not configured.

Proposed patch
 public function testSendSMS(): void
 {
-    $sender = new Telnyx(\getenv('TELNYX_API_KEY'));
+    $apiKey = \getenv('TELNYX_API_KEY') ?: null;
+    $from = \getenv('TELNYX_FROM') ?: null;
+    $to = \getenv('TELNYX_TO') ?: null;
+
+    if (!$apiKey || !$from || !$to) {
+        $this->markTestSkipped('TELNYX_API_KEY, TELNYX_FROM, and TELNYX_TO are required for this integration test.');
+    }
+
+    $sender = new Telnyx($apiKey, $from);
 
     $message = new SMS(
-        to: [\getenv('TELNYX_TO')],
+        to: [$to],
         content: 'Test Content',
-        from: \getenv('TELNYX_FROM')
+        from: $from
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$sender = new Telnyx(\getenv('TELNYX_API_KEY'));
// $message = new SMS(
// to: ['+18034041123'],
// content: 'Test Content',
// from: '+15005550006'
// );
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
from: \getenv('TELNYX_FROM')
);
$apiKey = \getenv('TELNYX_API_KEY') ?: null;
$from = \getenv('TELNYX_FROM') ?: null;
$to = \getenv('TELNYX_TO') ?: null;
if (!$apiKey || !$from || !$to) {
$this->markTestSkipped('TELNYX_API_KEY, TELNYX_FROM, and TELNYX_TO are required for this integration test.');
}
$sender = new Telnyx($apiKey, $from);
$message = new SMS(
to: [$to],
content: 'Test Content',
from: $from
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Messaging/Adapter/SMS/TelnyxTest.php` around lines 16 - 22, The test
fails because Telnyx is instantiated with too few arguments and doesn't guard
missing env vars; update the test in TelnyxTest to first read and validate
getenv('TELNYX_API_KEY'), getenv('TELNYX_TO') and getenv('TELNYX_FROM') and call
markTestSkipped (or return) if any are empty, then construct the Telnyx sender
passing the required from argument (e.g., include the from value when calling
new Telnyx(...)) and create the SMS using the validated env values before
calling send.

@deepshekhardas deepshekhardas force-pushed the feat/7811-telnyx-adapter branch from e416217 to 4621f3b Compare June 2, 2026 01:55
@deepshekhardas

Copy link
Copy Markdown
Author

Rebased onto latest main. Could you please review? 🙏

@greptile-apps

greptile-apps Bot commented Jun 2, 2026

Copy link
Copy Markdown

Greptile Summary

  • Refactors the Telnyx SMS adapter to require sender configuration at construction time.
  • Updates the adapter to send JSON payloads through the Telnyx v2 messages API.
  • Updates the Telnyx SMS test path to exercise the refactored adapter with environment-provided values.

Confidence Score: 3/5

The adapter and test changes need attention before merging because the sender contract can generate invalid Telnyx payloads and the default test path can depend on live SMS infrastructure.

The changed surface is small and the issues are localized to the Telnyx adapter/test behavior, but both affect practical correctness for supported configuration and repeatable test execution.

src/Utopia/Messaging/Adapter/SMS/Telnyx.php and tests/Messaging/Adapter/SMS/TelnyxTest.php

T-Rex T-Rex Logs

What T-Rex did

  • Created a focused Telnyx adapter harness that subclasses the adapter to intercept the outbound POST to https://api.telnyx.com/v2/messages and print the request JSON.
  • Attempted to run the harness, but the environment lacked a PHP binary, so execution was blocked.
  • Observed the before-state of a multirecipient Telnyx send, including the initial request and delivery result.
  • Observed the after-state of the multirecipient send, with two recipients and successful delivery.
  • Observed the before-state of the Telnyx error detail, showing an initial mocked POST and an Unknown error.
  • Observed the after-state of the Telnyx error detail, showing the same mocked POST but the error message changed to Telnyx says this destination is invalid.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (2): Last reviewed commit: "feat: add Telnyx SMS adapter (#7811)" | Re-trigger Greptile

Comment on lines +16 to +22
$sender = new Telnyx(\getenv('TELNYX_API_KEY'));

// $message = new SMS(
// to: ['+18034041123'],
// content: 'Test Content',
// from: '+15005550006'
// );
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
from: \getenv('TELNYX_FROM')
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 The Telnyx constructor now requires two arguments ($apiKey and $from), but only one is passed here. This will throw a PHP fatal error — "Too few arguments to function Telnyx::__construct(), 1 passed and exactly 2 expected" — making the entire test suite fail immediately. The TELNYX_FROM env var is already used for the SMS message, but needs to be passed to the adapter constructor as well.

Suggested change
$sender = new Telnyx(\getenv('TELNYX_API_KEY'));
// $message = new SMS(
// to: ['+18034041123'],
// content: 'Test Content',
// from: '+15005550006'
// );
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
from: \getenv('TELNYX_FROM')
);
$sender = new Telnyx(\getenv('TELNYX_API_KEY'), \getenv('TELNYX_FROM'));
$message = new SMS(
to: [\getenv('TELNYX_TO')],
content: 'Test Content',
);

Comment on lines +46 to 67
foreach ($message->getTo() as $to) {
$result = $this->request(
method: 'POST',
url: 'https://api.telnyx.com/v2/messages',
headers: [
'Content-Type: application/json',
"Authorization: Bearer {$this->apiKey}",
],
body: [
'from' => $this->from,
'to' => $to,
'text' => $message->getContent(),
]
);

if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->setDeliveredTo(\count($message->getTo()));
foreach ($message->getTo() as $to) {
if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->incrementDeliveredTo();
$response->addResult($to);
}
} else {
foreach ($message->getTo() as $to) {
$response->addResult($to, 'Unknown error.');
} else {
$response->addResult($to, $result['response']['errors'][0]['detail'] ?? 'Unknown error');
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Since getMaxMessagesPerRequest() returns 1, the parent send() method enforces that process() is always called with exactly one recipient and throws an exception otherwise. The foreach loop will therefore always iterate exactly once, making it misleading and inconsistent with the Twilio adapter (which uses $message->getTo()[0] directly). Accessing the single recipient directly is clearer and aligns with how other single-recipient adapters in this codebase are implemented.

Suggested change
foreach ($message->getTo() as $to) {
$result = $this->request(
method: 'POST',
url: 'https://api.telnyx.com/v2/messages',
headers: [
'Content-Type: application/json',
"Authorization: Bearer {$this->apiKey}",
],
body: [
'from' => $this->from,
'to' => $to,
'text' => $message->getContent(),
]
);
if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->setDeliveredTo(\count($message->getTo()));
foreach ($message->getTo() as $to) {
if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->incrementDeliveredTo();
$response->addResult($to);
}
} else {
foreach ($message->getTo() as $to) {
$response->addResult($to, 'Unknown error.');
} else {
$response->addResult($to, $result['response']['errors'][0]['detail'] ?? 'Unknown error');
}
}
$to = $message->getTo()[0];
$result = $this->request(
method: 'POST',
url: 'https://api.telnyx.com/v2/messages',
headers: [
'Content-Type: application/json',
"Authorization: Bearer {$this->apiKey}",
],
body: [
'from' => $this->from,
'to' => $to,
'text' => $message->getContent(),
]
);
if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) {
$response->setDeliveredTo(1);
$response->addResult($to);
} else {
$response->addResult($to, $result['response']['errors'][0]['detail'] ?? 'Unknown error');
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@deepshekhardas deepshekhardas force-pushed the feat/7811-telnyx-adapter branch from 4621f3b to 150212f Compare June 17, 2026 09:20
"Authorization: Bearer {$this->apiKey}",
],
body: [
'from' => $this->from,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Profile IDs are misplaced

The constructor now documents $from as either a Telnyx phone number or profile ID, but this request always sends that value in the from field. Telnyx uses a separate messaging_profile_id field for profile IDs, so a caller that passes a profile UUID as allowed by this adapter will send an invalid payload and the SMS will fail.

);

// $result = $sender->send($message);
$result = $sender->send($message);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Live send runs by default

This test now performs a real Telnyx send in the default PHPUnit suite. In normal CI or local runs without Telnyx credentials, test numbers, and network access, this path will fail or attempt to send a real SMS. The previous test was skipped for this reason, so this should stay gated on available credentials or use a mocked/request-catcher flow.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant